// app/api/procurement-rfqs/[rfqId]/vendors/[vendorId]/comments/route.ts import { NextRequest, NextResponse } from "next/server" import db from '@/db/db'; import { getServerSession } from "next-auth/next" import { authOptions } from "@/app/api/auth/[...nextauth]/route" import { procurementRfqComments, procurementRfqAttachments } from "@/db/schema" import { revalidateTag } from "next/cache" // 파일 저장을 위한 유틸리티 import { writeFile, mkdir } from 'fs/promises' import { join } from 'path' import crypto from 'crypto' /** * 코멘트 생성 API 엔드포인트 */ export async function POST( request: NextRequest, { params }: { params: { rfqId: string; vendorId: string } } ) { try { // 인증 확인 const session = await getServerSession(authOptions); if (!session?.user) { return NextResponse.json( { success: false, message: "인증이 필요합니다" }, { status: 401 } ) } const rfqId = parseInt(params.rfqId) const vendorId = parseInt(params.vendorId) // 유효성 검사 if (isNaN(rfqId) || isNaN(vendorId)) { return NextResponse.json( { success: false, message: "유효하지 않은 매개변수입니다" }, { status: 400 } ) } // FormData 파싱 const formData = await request.formData() const content = formData.get("content") as string const isVendorComment = formData.get("isVendorComment") === "true" const files = formData.getAll("attachments") as File[] if (!content && files.length === 0) { return NextResponse.json( { success: false, message: "내용이나 첨부파일이 필요합니다" }, { status: 400 } ) } // 코멘트 생성 const [comment] = await db .insert(procurementRfqComments) .values({ rfqId, vendorId, userId: parseInt(session.user.id), content, isVendorComment, isRead: !isVendorComment, // 본인 메시지는 읽음 처리 createdAt: new Date(), updatedAt: new Date(), }) .returning() // 첨부파일 처리 const attachments = [] if (files.length > 0) { // 디렉토리 생성 const uploadDir = join(process.cwd(), "public", `rfq-${rfqId}`, `vendor-${vendorId}`, `comment-${comment.id}`) await mkdir(uploadDir, { recursive: true }) // 각 파일 저장 for (const file of files) { const buffer = Buffer.from(await file.arrayBuffer()) const filename = `${Date.now()}-${crypto.randomBytes(8).toString("hex")}-${file.name.replace(/[^a-zA-Z0-9.-]/g, "_")}` const filePath = join(uploadDir, filename) // 파일 쓰기 await writeFile(filePath, buffer) // DB에 첨부파일 정보 저장 const [attachment] = await db .insert(procurementRfqAttachments) .values({ rfqId, commentId: comment.id, fileName: file.name, fileSize: file.size, fileType: file.type, filePath: `/rfq-${rfqId}/vendor-${vendorId}/comment-${comment.id}/${filename}`, isVendorUpload: isVendorComment, uploadedBy: parseInt(session.user.id), vendorId, uploadedAt: new Date(), }) .returning() attachments.push({ id: attachment.id, fileName: attachment.fileName, fileSize: attachment.fileSize, fileType: attachment.fileType, filePath: attachment.filePath, uploadedAt: attachment.uploadedAt }) } } // 캐시 무효화 revalidateTag(`rfq-${rfqId}-comments`) // 응답 데이터 구성 const responseData = { id: comment.id, rfqId: comment.rfqId, vendorId: comment.vendorId, userId: comment.userId, content: comment.content, isVendorComment: comment.isVendorComment, createdAt: comment.createdAt, updatedAt: comment.updatedAt, userName: session.user.name, attachments, isRead: comment.isRead } return NextResponse.json({ success: true, data: { comment: responseData } }) } catch (error) { console.error("코멘트 생성 오류:", error) return NextResponse.json( { success: false, message: "코멘트 생성 중 오류가 발생했습니다" }, { status: 500 } ) } }